Skip to content

Conversation

@amircheikh
Copy link
Collaborator

@amircheikh amircheikh commented Oct 28, 2025

Cleanup and various fixes for sdk_flutter and demo app.

New features:

  • signMessage method added. Signs a plaintext message using the specified wallet account. Automatically determines the payload encoding and hash function based on the wallet account's address format, unless explicitly overridden
  • autoFetchWalletKitConfig can now be disabled in the TurnkeyProvider config
  • PasskeyConfig is now configurable in the TurnkeyProvider config. This allows you to set an rpId and rpName for all functions that need them
  • createClient and createPasskeyClient functions are now exposed
  • fetchOrCreateP256ApiKeyUser method added. Fetches an existing user by P-256 API key public key, or creates a new one if none exists.
  • fetchOrCreatePolicies method added. Fetches each requested policy if it exists, or creates it if it does not.

Bug fixes / minor improvements:

  • authProxyBaseUrl is now properly defaulted
  • sessionExpirationSeconds is now properly considered for passkey auth
  • handleAppleOauth now correctly uses Turnkey's OAuth Proxy url
  • Flutter lint-ing suggestions are solved in demo app
  • Various code + comment cleanup

@amircheikh amircheikh requested a review from moeodeh3 November 11, 2025 21:55
Copy link
Collaborator

@moeodeh3 moeodeh3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one big organizational note - Flutter/Dart supports a feature similar to Swift where you can define a single class across multiple files (via extension). If we can logically split things up instead of keeping everything in one large turnkey.dart file, that would be awesome

I'd recommend taking a look at our Swift SDK setup seen here - it’s a bit of a hassle to organize initially, but it improves readability a lot and I think this is worth it

npm run build
npm start
```
You can find your `ORGANIZATION_ID` and `AUTH_PROXY_CONFIG_ID` from the [Turnkey Dashboard](https://app.turnkey.com).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

),
body: LoginScreen(),
);
// We'll have the `onSessionSelected` callback navigate to the dashboard screen. You can also add another case here for AuthState.authenticated if you want to handle it directly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a bit odd to me - I think we should just exhaust the switch statement and handle every AuthState, rather than relying on onSessionSelected here to take us to the dashboard screen

}

final usingAuthProxy = (config.authProxyConfigId ?? '').isNotEmpty;
final usingAuthProxy = (config.authProxyConfigId ?? '').isNotEmpty &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this no longer tells us if they are just using authProxy, but instead tells us if they are using it and have the wallet-kit fetch enabled, so I think lets rename this

usingAuthProxy -> authProxyFetchEnabled

final usingAuthProxy = (config.authProxyConfigId ?? '').isNotEmpty &&
config.authConfig?.autoFetchWalletKitConfig == true;
if (usingAuthProxy) {
if (config.authConfig?.sessionExpirationSeconds != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design choice: I know this was copied from JS, but in Swift we decided not to allow passing sessionExpirationSeconds in the config because:

  1. the console warning is misleading, this value is only used for Passkeys (and wallets in JS)
  2. it creates confusion for users who don’t have deep Turnkey infrastructure knowledge, since it’s unclear that it only applies to certain auth methods and is ignored for most

instead we removed it from the general config and require it to be passed directly into the relevant auth functions (e.g. loginWithPasskey())

this is one of the breaking changes I plan on making for js! So lets get ahead of it here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think thats necessary, I think the warning is sufficient and it provides clarity on the frontend what your auth proxy config is if you ever need to access it in the app.

: config.authConfig?.sessionExpirationSeconds ??
AUTH_DEFAULT_EXPIRATION_SECONDS);

final otpAlphanumeric = proxyAuthConfig?.otpAlphanumeric ??
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay having otpAlphanumeric is useless I think in our mobile sdks?

In js we do this I think because we have UI components, and if someone wanted to use the authProxy with our UI components without having to fetch for wallet-kit config, they can

here that doesn't apple and I think there is literally no point of having this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking deeper here, we have a lot of stuff blindly copied over thats useless and cluttering our config. Can you copy what we do in swift seen here instead

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while you're at it, want to also rename:

masterConfig -> runtimeConfig

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config includes everything for clarity, that way you don't have to query the auth proxy when you wan't to figure out what config it's running. It's also only clutter if you try to set it, which it specifically tells you not to...


session = s;
_createClient(
createClient(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also need to pass in authProxyId and authProxyUrl here so the client can make requests to the Auth Proxy. For example, an authenticated user might need to call the Auth Proxy when updating their email and keeping it verified - they’d use it to send and verify the OTP

can you check all createClient() calls and make sure they all have this passed in!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nvm our createClient() helper already does this I think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulls from masterConfig

bool? invalidateExisting,
void Function(String oidcToken)? onSuccess,
String? publicKey,
void Function(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing to flag is our onSuccess() in js returns any, I don't think this matters though


String toUtf8String(Uint8List bytes) => utf8.decode(bytes);

String bytesToHex(Uint8List bytes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this and use uint8ArrayToHexString() from turnkey_encoding

const CreateP256UserParams({this.userName, this.apiKeyName});
}

class PolicyWithId extends v1CreatePolicyIntentV3 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Policies have ids, its just in our fetchOrCreatePolicies() we basically have a v1Policy minus the created_at and updated_at timestamps, and we don't want to have to do another query to get those, so we just created another type

anyways I think we should call this just Policy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's to assuming this isn't breaking 🍻

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

net new function so we are okay!

/// [invalidateExisting] Optional flag to invalidate existing sessions when logging in or signing up.
/// [publicKey] Optional public key to use for the session. If null, a new key pair is generated.
/// [onSuccess] Optional callback function that receives the oidcToken, publicKey and providerName upon successful authentication, overrides default behavior.
Future<void> handleXOAuth({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we fix state validation here, we have state isn't acting as state. Lets do this for OAuth2 functions (X, Discord)

you can look at what we do here in Swift

),
body: LoginScreen(),
);
// We'll have the `onSessionSelected` callback navigate to the dashboard screen. You can also add another case here for AuthState.authenticated if you want to handle it directly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for this anymore

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should be able to remove the onSessionSelected() from the example as well!

void onSessionSelected(Session session) {
    if (isValidSession(session)) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        navigatorKey.currentState?.pushReplacement(
          MaterialPageRoute(builder: (context) => const DashboardScreen()),
        );
        final ctx = navigatorKey.currentContext;
        if (ctx != null) {
          ScaffoldMessenger.of(ctx).showSnackBar(
            const SnackBar(
              content: Text('Logged in! Redirecting to the dashboard.'),
            ),
          );
        }
      });
    }
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah that just shows a loading bar now when we are authenticated but we haven't heard from the onSessionSelected callback so we don't flash the login screen.

/// [rpId] The Relying Party ID to use for Passkey authentication. If null, the value from the config's PasskeyStamperConfig will be used.
/// [overrideExisting] Whether to override the existing client instance with the newly created one. If true, all helper functions within the TurnkeyProvider will be using this client and thus, will be stamping using a passkey. Defaults to false.
/// Returns the newly created TurnkeyClient instance configured for Passkey stamping.
TurnkeyClient createPasskeyClient(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move all the passkey stuff into its own extension as well 🙏

}

// --- resolved methods ------------------------------------------------------
final resolvedMethods = AuthMethods(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this

@moeodeh3 moeodeh3 merged commit ea6e18a into main Nov 14, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants